const sm2 = require('sm-crypto').sm2;
const sm3 = require('sm-crypto').sm3;
const sm4 = require('sm-crypto').sm4;
const compressing = require('compressing');
const fs = require("fs");
/**
 * decrypt data and check signature
 * @param chunk data,buffer
 * @param sm4key sm4key,array of hex
 * @param sm2publicKey sm2publickey,string
 * @param eginfo information including signature,object
 */
function DecryptData(chunk,sm4key,sm2publicKey,seginfo){
    return new Promise(function(resolve, reject) {
        if(seginfo.SegmentSize==seginfo.EncryptedSegSize){
            decryptData = sm4.decrypt(chunk,sm4key);
            var value =  Buffer.alloc(seginfo.SegmentSize);
            for (var i = 0;i < seginfo.SegmentSize;i++) {
                value[i] = decryptData[i];
            }
        }
        else{
            var _decryptData = sm4.decrypt(chunk,sm4key);
            var value =  Buffer.alloc(seginfo.SegmentSize);
            for (var i = 0;i < seginfo.SegmentSize;i++) {
                value[i] = _decryptData[i];
            }
        }
        if(sm2.doVerifySignature(value, seginfo.SegSign,sm2publicKey,{hash:true})){
            resolve(value)
        }
        else{
            reject({'status':406,'error': 'wrong sign!'});
        }
    });
}

/**
 * decryptfile
 * @param infile Encrypted file path,string
 * @param outfile Decrypted file path,string
 * @param sm4key sm4key,array of hex
 * @param sm2publicKey sm2publickey,string
 * @param EncryptionInformation information,object
 */
function decryptfile(infile, outfile, sm4key, sm2publicKey,EncryptionInformation){
    return new Promise(function(resolve, reject) {
        let rs=fs.createReadStream(infile,{highWaterMark:1048576,flag:'binary'});
        var numofseg=0;
        rs.on('data',function (chunk) {  
            DecryptData(chunk,sm4key,sm2publicKey,EncryptionInformation.Segment[numofseg]).then((value) =>{
                numofseg++;
                fs.writeFile(outfile, value, {flag:'a+'},  function(err) {
                    if (err) {
                        reject(err);
                    }
                });
            },
            function(error){
                console.log(error);
                reject(error);
            });
        });
        rs.on('end',function () {
            if(EncryptionInformation.SegmentNumber==numofseg){
                resolve('decryption succeeded!');
            }
            else{
                reject({'status':405,'error':'payload broken!'});
            }
        });
        rs.on('error',function (err) {
            reject(err);
        });
        
    });
}

/**
 * rule check
 * @param rules rules,array
 * @param sys operating system,string
 * @param ipaddr ipv4,string
 */
function checkrule(rules, sys, ipaddr){
    return new Promise(function(resolve,reject){
        var num=rules.length;
        for (i=0;i<num;i++){
            rule=rules[i];
            switch(rule.id){
                case 1:
                    var myDate = new Date();
                    if(rule.parameters.begintime<myDate.getTime()<rule.parameters.endtime){

                    }
                    else{
                        reject({'status':404,
                        'error':'invalid time!'});
                    }
                        
                    break;
                case 2:
                    if(ipaddr==rule.parameters.ipaddr){

                    }
                    else{
                        reject({'status':402,
                        'error':'invalid IP!'});
                    }
                        
                    break;
                case 3:
                    if(sys==rule.parameters.os){

                    }
                    else{
                        reject({'status':403,
                        'error':'invalid OS!'});
                    }
                    break;
                default:
                    reject({'status':400,
                        'error':'invalid capsule!'});
                    break; 
            }
        }
        resolve(true);
    });
}

/**
 * read permission file
 * @param sessionid session id for capsule,string
 */
function readpermission(sessionid){
    return new Promise(function(reslove,reject){
        var permission=JSON.parse(fs.readFileSync(__dirname+'/permission/'+sessionid+'.json'));
        if('SessionKey' in permission){
            var sm4key=permission.SessionKey;
        }
        else{
            reject({'status':401,'error':'invalid permission!'});
        }
        if('PubKey' in permission){
            var sm2publicKey=permission.PubKey;
        }
        else{
            reject({'status':401,'error':'invalid permission!'});
        }
        reslove({'sm4key':sm4key,'sm2publicKey':sm2publicKey});
    });
}


/**
 * read information
 * @param info file path for info.json,string
 */
function readinfo(info){
    return new Promise(function(reslove,reject){
        var information=JSON.parse(fs.readFileSync(info));
        if(('KeyAccess' in information)&&('EncryptionInformation' in information)&&('Payload' in information)&&('Payload' in information)&&('Rule' in information)){
            reslove(information);
        }
        else{
            reject({'status':400,'error':'invalid capsule!'});
        }
    });
}


/**
 * Open capsule,and decrypt payload after checking the rules.
 * @param capsule file path of capsule
 * @param dest destination file path for payload 
 * @param userinfo user information,example:{'sys':'windows','ipaddr':'127.0.0.1'}
 */
async function OpenCapsule(capsule,dest,userinfo){
    var _tmpfile=__dirname+'/capsule/';
    if(fs.existsSync(capsule)){
        await compressing.zip.uncompress(capsule,_tmpfile);
        var _file=_tmpfile+capsule.split('/').pop().split('.')[0];
        var information=await readinfo(_file+'/info.json').catch(err=>{
            console.log(err);
            throw(err);
        });
        var permission=await readpermission(information.KeyAccess.SessionId).catch(err=>{
            console.log(err);
            throw(err);
        });
        if(information.Rule.AdditionalRules){
            var pass= await checkrule(information.Rule.Rules,userinfo.sys,userinfo.ipaddr).catch(err=>{
                console.log(err);
                throw(err);
            });
        }
        else{
            var pass=true;
        }
        if(pass){
            if(information.Payload.isEncrypted){
                decryptfile(_file+'/0.payload',dest+'/'+information.Payload.fileName+'.'+information.Payload.ext,permission.sm4key,permission.sm2publicKey,information.EncryptionInformation)
                .then((value)=>{
                    console.log(value);
                    return({'status':'200','dest':dest+'/'+information.Payload.filename+'.'+information.Payload.ext});
                },
                function(err){
                    console.log(err);
                    throw(err);
                })
            }
        }
    }
    else{
        throw({'status':400,'error':'invalid capsule'});
    }
}


module.exports=OpenCapsule;